//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using ICloneable = System.ICloneable;
using Flare.Events;
using UnityEngine;

namespace Flare.Media
{
    /// <summary>
    /// The Sound class allows audio defined in a SWF file to be accessed and played.
    /// </summary>
    public class Sound : EventDispatcher, ICloneable
    {
        private const string soundGameObjectName = "_FlareSound";

        private static GameObject ms_soundGameObject;

        /// <summary>
        /// Returns the length of the sound in milliseconds.
        /// </summary>
        public float length
        {
            get { return 1000.0f * m_clip.length; }
        }

        private AudioClip m_clip;
        
        internal Sound(AudioClip clip)
        {
            this.m_clip = clip;

            // Check if the Flare sound game object exist, and if not create it
            if (Sound.ms_soundGameObject == null)
            {
                Sound.ms_soundGameObject = GameObject.Find(Sound.soundGameObjectName);
                if (Sound.ms_soundGameObject == null)
                {
                    Sound.ms_soundGameObject = new GameObject(Sound.soundGameObjectName);
                    Sound.ms_soundGameObject.hideFlags = HideFlags.HideAndDontSave;
                }
            }
        }
        
        internal void Play(bool noMultiple)
        {
            AudioSource[] sources = Sound.ms_soundGameObject.GetComponents<AudioSource>();

            // See if there's an existing instance playing
            if (noMultiple)
            {
                for (int i = 0; i < sources.Length; ++i)
                {
                    if ((sources[i].clip == this.m_clip) && sources[i].isPlaying)
                    {
                        // At least one other instance is playing so do not start another instance
                        return;
                    }
                }
            }

            // Find a free audio source and use it to play the sound
            AudioSource source = null;
            for (int i = 0; i < sources.Length; ++i)
            {
                if (!sources[i].isPlaying)
                {
                    source = sources[i];
                    break;
                }
            }

            // No free audio source so create a new one
            if (source == null)
            {
                source = Sound.ms_soundGameObject.AddComponent<AudioSource>();
            }

            // Set the audio clip and start playing the sound
            if (source != null)
            {
                source.clip = this.m_clip;
                source.Play();
            }
        }
        
        internal void Stop()
        {
            AudioSource[] sources = Sound.ms_soundGameObject.GetComponents<AudioSource>();

            // Stop all of the audio sources using this clip
            for (int i = 0; i < sources.Length; ++i)
            {
                if (sources[i].clip == this.m_clip)
                {
                    sources[i].Stop();
                }
            }
        }
            
        /// <summary>
        /// Creates a new SoundChannel object to play back the sound.
        /// </summary>
        public SoundChannel Play(float startTime = 0, int loops = 0, SoundTransform sndTransform = null)
        {
            // TODO bwmott 2013-11-24: The audio sources used for this should be another set to avoid
            // conflicts with the timeline based sounds. An audio source used by script based playing
            // should be reserved until the corresponding SoundChannel is no longer used.

            if (startTime != 0 || loops != 0)
            {
                throw new global::System.ArgumentException("startTime and loops are not supported");
            }

            AudioSource[] sources = Sound.ms_soundGameObject.GetComponents<AudioSource>();

            // Find a free audio source and use it to play the sound
            AudioSource source = null;
            for (int i = 0; i < sources.Length; ++i)
            {
                if (!sources[i].isPlaying)
                {
                    source = sources[i];
                    break;
                }
            }

            // No free audio source so create a new one
            if (source == null)
            {
                source = Sound.ms_soundGameObject.AddComponent<AudioSource>();
            }

            // Set the audio clip and start playing the sound
            if (source != null)
            {
                source.clip = this.m_clip;
                return new SoundChannel(source, sndTransform);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Return a clone of the Sound with a deep copy of any mutable members.
        /// </summary>
        public override object Clone()
        {
            Sound clone = (Sound)base.Clone();

            return clone;
        }
    }
}
